package com.fll.server.handlers;

import com.fll.protocol.Constants;
import com.fll.protocol.ProxyMessage;
import com.fll.server.ProxyChannelManager;
import com.fll.server.config.ProxyConfig;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * @author fanlei
 * @version 1.0
 * @date 2019年01月25日
 */
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
    private static Logger logger = LoggerFactory.getLogger(ServerChannelHandler.class);

    public ServerChannelHandler() {
    }

    protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
        logger.debug("ProxyMessage received {}", proxyMessage.getType());
        switch(proxyMessage.getType()) {
            case 1:
                this.handleAuthMessage(ctx, proxyMessage);
            case 2:
            case 6:
            default:
                break;
            case 3:
                this.handleConnectMessage(ctx, proxyMessage);
                break;
            case 4:
                this.handleDisconnectMessage(ctx, proxyMessage);
                break;
            case 5:
                this.handleTransferMessage(ctx, proxyMessage);
                break;
            case 7:
                this.handleHeartbeatMessage(ctx, proxyMessage);
        }

    }

    private void handleTransferMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        Channel userChannel = (Channel)ctx.channel().attr(Constants.NEXT_CHANNEL).get();
        if (userChannel != null) {
            ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
            buf.writeBytes(proxyMessage.getData());
            userChannel.writeAndFlush(buf);
        }

    }

    private void handleDisconnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        String clientKey = (String)ctx.channel().attr(Constants.CLIENT_KEY).get();
        Channel userChannel;
        if (clientKey == null) {
            String userId = proxyMessage.getUri();
            userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(ctx.channel(), userId);
            if (userChannel != null) {
                userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            }

        } else {
            Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
            if (cmdChannel == null) {
                logger.warn("ConnectMessage:error cmd channel key {}", ctx.channel().attr(Constants.CLIENT_KEY).get());
            } else {
                userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, (String)ctx.channel().attr(Constants.USER_ID).get());
                if (userChannel != null) {
                    userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
                    ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
                    ctx.channel().attr(Constants.CLIENT_KEY).remove();
                    ctx.channel().attr(Constants.USER_ID).remove();
                }

            }
        }
    }

    private void handleConnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        String uri = proxyMessage.getUri();
        if (uri == null) {
            ctx.channel().close();
            logger.warn("ConnectMessage:null uri");
        } else {
            String[] tokens = uri.split("@");
            if (tokens.length != 2) {
                ctx.channel().close();
                logger.warn("ConnectMessage:error uri");
            } else {
                Channel cmdChannel = ProxyChannelManager.getCmdChannel(tokens[1]);
                if (cmdChannel == null) {
                    ctx.channel().close();
                    logger.warn("ConnectMessage:error cmd channel key {}", tokens[1]);
                } else {
                    Channel userChannel = ProxyChannelManager.getUserChannel(cmdChannel, tokens[0]);
                    if (userChannel != null) {
                        ctx.channel().attr(Constants.USER_ID).set(tokens[0]);
                        ctx.channel().attr(Constants.CLIENT_KEY).set(tokens[1]);
                        ctx.channel().attr(Constants.NEXT_CHANNEL).set(userChannel);
                        userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
                        userChannel.config().setOption(ChannelOption.AUTO_READ, true);
                    }

                }
            }
        }
    }

    private void handleHeartbeatMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        ProxyMessage heartbeatMessage = new ProxyMessage();
        heartbeatMessage.setSerialNumber(heartbeatMessage.getSerialNumber());
        heartbeatMessage.setType((byte)7);
        logger.debug("response heartbeat message {}", ctx.channel());
        ctx.channel().writeAndFlush(heartbeatMessage);
    }

    private void handleAuthMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        String clientKey = proxyMessage.getUri();
        List<Integer> ports = ProxyConfig.getInstance().getClientInetPorts(clientKey);
        if (ports == null) {
            logger.info("error clientKey {}, {}", clientKey, ctx.channel());
            ctx.channel().close();
        } else {
            Channel channel = ProxyChannelManager.getCmdChannel(clientKey);
            if (channel != null) {
                logger.warn("exist channel for key {}, {}", clientKey, channel);
                ctx.channel().close();
            } else {
                logger.info("set port => channel, {}, {}, {}", new Object[]{clientKey, ports, ctx.channel()});
                ProxyChannelManager.addCmdChannel(ports, clientKey, ctx.channel());
            }
        }
    }

    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        Channel userChannel = (Channel)ctx.channel().attr(Constants.NEXT_CHANNEL).get();
        if (userChannel != null) {
            userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
        }

        super.channelWritabilityChanged(ctx);
    }

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel userChannel = (Channel)ctx.channel().attr(Constants.NEXT_CHANNEL).get();
        if (userChannel != null && userChannel.isActive()) {
            String clientKey = (String)ctx.channel().attr(Constants.CLIENT_KEY).get();
            String userId = (String)ctx.channel().attr(Constants.USER_ID).get();
            Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
            if (cmdChannel != null) {
                ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
            } else {
                logger.warn("null cmdChannel, clientKey is {}", clientKey);
            }

            userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            userChannel.close();
        } else {
            ProxyChannelManager.removeCmdChannel(ctx.channel());
        }

        super.channelInactive(ctx);
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("exception caught", cause);
        super.exceptionCaught(ctx, cause);
    }
}
